﻿using Easyliter;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
//using System.Threading.Tasks;
using System.Windows.Forms;
using UploadForm.Common;
using UploadForm.Model;

namespace UploadForm
{
    public partial class DeviceForm : Form
    {

        /// <summary>
        /// 是否有文件在上传的公共变量
        /// </summary>
        private static bool IS_UPLOADING = false;

        /// <summary>
        /// 是否已经给用记提示过上传失败，如果已经提示过，则不再提示了
        /// </summary>
        private static bool HAVE_SHOW_ERROR = false;

        /// <summary>
        /// 保存文件缓存的本地数据库
        /// </summary>
        public static Easyliter.Client Sqlite { get; private set; }

        string serverPath = "http://192.168.1.159:8000";

        public DeviceForm()
        {
            InitializeComponent();
            this.StartPosition = FormStartPosition.CenterScreen;

            //查看是否已经有本地数据库，并创建相应的数据库及数据表
            string dbFile = Path.Combine(Directory.GetCurrentDirectory(), "DB.sqlite");
            bool isCreateTable = false;
            if (!File.Exists(dbFile))
            {
                SQLiteConnection.CreateFile(dbFile);
                isCreateTable = true;
            }
            //SqlConnection = new SqLiteHelper($"Data Source={dbFile};Version=3;");

            var connstr = "DataSource=" + dbFile;
            Sqlite = new Client(connstr);

            if (isCreateTable)
            {
                //SqlConnection.ExecuteQuery(@"
                //    Create Table UploadItem(
                //        Id integer PRIMARY KEY,
                //        TaskId Text,
                //        UserName Text,
                //        FilePath Text,
                //        UploadState Integer,
                //        Chunk Integer,
                //        IsMergeFailed Integer
                //    )");
                Sqlite.ExecuteNonQuery(@"
                    Create Table UploadItem(
                        Id integer PRIMARY KEY,
                        TaskId Text,
                        UserName Text,
                        FilePath Text,
                        UploadState Integer,
                        Chunk Integer,
                        IsMergeFailed Integer
                    )");

                ////这里简化做法，不使用多线程的方式去记录错误的块，再重传。 只单线程记录就好
                //SqlConnection.ExecuteQuery(@"
                //    Create Table SliceUploadState(
                //        Id integer PRIMARY KEY,
                //        UploadItemId Text,
                //        Chunk Text,
                //        UploadState Integer,
                //        RetryTime Integer
                //    )");
            }

            this.FormClosing += (o, e) =>
            {
                if (IS_UPLOADING)
                {
                    if (MessageBox.Show("还有上传任务进行中，确定退出程序吗？", "系统提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) == DialogResult.Yes)
                    {
                        e.Cancel = false;
                    }
                    else
                    {
                        e.Cancel = true;
                    }
                }
            };
            //后台线程，监听上传任务
            ThreadPool.QueueUserWorkItem(new WaitCallback(Upload));
        }        

        /// <summary>
        /// 
        /// </summary>
        private void Upload(Object state)
        {
            //return;
            try
            {
                while (true)
                {
                    //加载相应的上传任务，进行后台上传 To do..
                    //SQLiteDataReader reader = SqlConnection.ReadFullTable("UploadItem");
                    //var uploadList = Utils.GetList<UploadItem>(reader);

                    var uploadList = Sqlite.Select<UploadItem>("select * from UploadItem");
                    if (uploadList.Count > 0)
                    {
                        IS_UPLOADING = true;
                    }
                    else
                    {
                        IS_UPLOADING = false;
                    }
                    


                    foreach (var upload in uploadList)
                    {
                        if (upload.IsMergeFailed)
                        {
                            this.MergeFileSlice(upload);
                        }
                        else
                        {
                            //上传切片文件
                            if (this.Upload(upload))
                            {
                                //在所有切片均上传完成后，再调用合并的接口。
                                this.MergeFileSlice(upload);
                            }
                            else
                            {
                                //如果中间有错误，则中止传输
                                break;
                            }
                        }
                        label_netSpeed.BeginInvoke(new Action(() => { label_netSpeed.Text = $"当前网速:-KB"; }));
                        label_netTime.BeginInvoke(new Action(() => { label_netTime.Text = $"该条上传时间：-分钟"; }));
                    }
                    Thread.Sleep(3000);
                }
            }
            catch (Exception ex)
            {
                Logger.WriteLog("数据上传出错：", ex);
            }
        }

        /// <summary>
        /// 上传分片数据
        /// </summary>
        /// <param name="fileFullName"></param>
        /// <param name="chunk"></param>
        /// <param name="taskId"></param>
        /// <returns></returns>
        private bool Upload(UploadItem upload)
        {
            string fileFullName = upload.FilePath;
            int chunk = upload.Chunk;
            string taskId = upload.TaskId;
            bool result = true;
            string responseText = "";
            FileStream fStream = new FileStream(fileFullName, FileMode.Open, FileAccess.Read);
            BinaryReader bReader = new BinaryReader(fStream);

            //一次只上传 1M 。这里数据不能超过2M ，因为服务端会把切片数据放到内存中，数据量过大，测试机内存撑不住
            int length = 1 * 1024 * 1024;
            string fileName = Path.GetFileName(fileFullName);

            FileInfo fi = new FileInfo(fileFullName);
            int totalChunk = (int)Math.Floor(fi.Length / (float)length);

            Stopwatch timeCounter = new Stopwatch();

            try
            {
                byte[] data;

                int index = 0;
                while (true)
                {
                    timeCounter.Start();

                    index++;
                    data = bReader.ReadBytes(length);
                    if (chunk != 0 && index <= chunk)
                        continue;
                    if (data.Length == 0)
                        break;

                    HttpRequestClient httpRequestClient = new HttpRequestClient();
                    httpRequestClient.SetFieldValue("task_id", taskId);
                    httpRequestClient.SetFieldValue("chunk", chunk.ToString());
                    var md5 = Utils.GetMD5HashFromBytes(data);

                    //需要使用md5检验文件流是否正确
                    httpRequestClient.SetFieldValue("md5", md5);
                    httpRequestClient.SetFieldValue("file", fileName, "application/octet-stream", data);
                    httpRequestClient.Upload(serverPath + "/upload/start/", out responseText);

                    #region 更新UI的各个显示
                    //更新上传进度
                    Sqlite.Update<UploadItem>(new UploadItem() { Chunk = chunk }, new { Id = upload.Id });
                    progressBar1.BeginInvoke(new Action(() => { progressBar1.Value = (int)Math.Floor((chunk * 100 / (float)totalChunk)); }));

                    timeCounter.Stop();
                    var time = (float)timeCounter.ElapsedMilliseconds / 1000;

                    var speedString = string.Empty;
                    var speedM = (float)length / 1024 / 1024 / time;
                    if (speedM > 1)
                    {
                        speedString = speedM + "M";
                    }
                    else
                    {
                        speedString = speedM * 1024 + "KB";
                    }
                    label_netSpeed.BeginInvoke(new Action(() => { label_netSpeed.Text = $"当前网速:{ speedString }"; }));

                    TimeSpan ts = new TimeSpan(0, 0, (int)((totalChunk - chunk) * time));
                    var timeString = string.Empty;
                    if (ts.Hours != 0)
                    {
                        timeString += ts.Hours + "小时";
                    }
                    else if (ts.Minutes != 0)
                    {
                        timeString += ts.Minutes + "分钟";
                    }
                    else
                    {
                        timeString += ts.Seconds + "秒";
                    }

                    label_netTime.BeginInvoke(new Action(() => { label_netTime.Text = $"该条上传时间：{timeString}"; }));
                    timeCounter.Reset();
                    #endregion

                    if (data.Length < length)
                    {
                        //已经没有数据可读了
                        break;
                    }
                    chunk++;
                }
            }
            catch (Exception ex)
            {
                if (!HAVE_SHOW_ERROR)
                {
                    MessageBox.Show("上传失败，请检查网络是否连接正常，程序将稍后自动重试");
                    HAVE_SHOW_ERROR = true;
                }
                result = false;
            }

            fStream.Close();
            bReader.Close();

            return result;
        }

        private void MergeFileSlice(UploadItem upload)
        {
            string taskId = upload.TaskId;
            string fileName = Path.GetFileName(upload.FilePath);
            //发起合并请求
            var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(serverPath + $"/upload/merge?task_id={taskId}&filename={fileName}");
            var response = (System.Net.HttpWebResponse)request.GetResponse();
            var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();

            //这里的判断并不严谨。不过先满足业务需求再说
            if (responseString.Length > 100)
            {
                //如果合并完成，则从上传队列中删除上传任务、删除数据库记录、删除原始文件  To do..
                Sqlite.ExecuteNonQuery($"Delete From UploadItem where id={upload.Id}");

                Thread.Sleep(1000);
            }
            else
            {
                //合并操作失败，写入数据库 To do..
                Sqlite.Update<UploadItem>(new UploadItem() { IsMergeFailed = true }, new { Id = upload.Id });
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (!File.Exists(textBox1.Text)) {
                MessageBox.Show("所选文件路径不存在");
            }

            //将文件写入到上传队列中..
            Sqlite.Insert<UploadItem>(new UploadItem() {
                Id = 10,
                TaskId = Guid.NewGuid().ToString("N"),
                Chunk = 0,
                FilePath = textBox1.Text,
                UploadState = 0,
                IsMergeFailed = false
            });
        }
        
    }
}
